﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using KaytechSoftware.Cms.Entities.Interfaces;

namespace KaytechSoftware.Cms.Entities
{
    public class Repository<T> : IRepository<T> where T : class, IEntity
    {
        private readonly IUnitOfWorkService _unitOfWorkService;
        private IUnitOfWork _unitOfWork;
        private Thread _lastUsedThread;
        private readonly object _syncRoot = new object();

        public Repository(IUnitOfWorkService unitOfWorkService)
        {
            _unitOfWorkService = unitOfWorkService;
        }

        private DbContext Context
        {
            get
            {
                ThreadCheck();

                if (_unitOfWork == null)
                {
                    _unitOfWork = _unitOfWorkService.GetCurrentUnitOfWork();
                    _unitOfWorkService.RegisterRepository(_unitOfWork, this);
                }
                return _unitOfWork.Context;
            }
        }

        private void ThreadCheck()
        {
            lock (_syncRoot)
            {
                if (_lastUsedThread == null)
                {
                    _lastUsedThread = Thread.CurrentThread;
                }
                else
                {
                    if (_lastUsedThread != Thread.CurrentThread)
                    {
                        throw new InvalidOperationException("Non-thread safe context use!");
                    }
                }
            }
        }

        #region IRepository<T> Members

        public void AttachTo(IUnitOfWork unitOfWork)
        {
            if (_unitOfWork != null)
            {
                _unitOfWorkService.UnregisterRepository(_unitOfWork, this);
            }
            _unitOfWork = unitOfWork;
            _unitOfWorkService.RegisterRepository(_unitOfWork, this);
        }

        public void DetachFrom(IUnitOfWork unitOfWork)
        {
            _unitOfWork = null;
        }

        public IQueryable<T> GetAll()
        {
            return Context.Set<T>();
        }

        public IQueryable<T> GetAll(Expression<Func<T, object>> includeExpression)
        {
            return GetAll().Include(includeExpression);
        }

        public IQueryable<T> GetAll(IEnumerable<Expression<Func<T, object>>> includeExpressions)
        {
            var dbSet = GetAll();
            return includeExpressions.Aggregate(dbSet, (current, expression) => current.Include(expression));
        }

        public T GetById(long id)
        {
            return GetAll().FirstOrDefault(x => x.Id == id);
        }

        public void Add(T entity)
        {
            var entry = Context.Entry(entity);
            entry.State = EntityState.Added;
        }

        public void Edit(T entity)
        {
            var entry = Context.Entry(entity);
            entry.State = EntityState.Modified;
        }

        public void Delete(T entity)
        {
            var entry = Context.Entry(entity);
            entry.State = EntityState.Deleted;
        }

        #endregion
    }
}
